package cn.wen.javax.net.ftp;

import it.sauronsoftware.ftp4j.FTPClient;
import it.sauronsoftware.ftp4j.FTPFile;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.log4j.Logger;

/**
 * Title：FTP 辅助类
 * <p>
 * Description：
 * <p>
 * Copyright：Copyright (c) 2010-2012
 * 
 * @author <a href="mailto:ct_wen@qq.com">ctwen</a>, 温承瑭
 * @version Revision：1.0 Date：2012/6/29 Description：初完成。
 *          <p>
 */
public final class HelperOfFTP4J {

    private HelperOfFTP4J() {
    }

    private static final Logger log = Logger.getLogger(HelperOfFTP4J.class);

    /**
     * 创建与目标主机的FTP连接，使用FTP默认端口21，成功 => 返回客户端，失败 => null。
     * 
     * @param host 主机名、IP
     * @param username FTP用户名
     * @param password FTP密码
     * 
     * @return FTP客户端 或 null。
     */
    public static FTPClient getFTPClient(String host, String username, String password) throws FTP4JException {
	return getFTPClient(host, 21, username, password);
    }

    /**
     * 创建与目标主机的FTP连接，成功 => 返回客户端，失败 => null。
     * 
     * @param host 主机名、IP
     * @param port ftp端口号
     * @param username ftp用户名
     * @param password ftp密码
     * 
     * @return FTP客户端 或 null。
     */
    public static FTPClient getFTPClient(String host, int port, String username, String password) throws FTP4JException {
	try {
	    FTPClient client = new FTPClient();
	    client.connect(host, port);
	    client.login(username, password);
	    // client.setPassive(true);
	    // client.setType(FTPClient.TYPE_BINARY);
	    // client.setAutoNoopTimeout(10);
	    return client;
	}
	catch (Exception e) {
	    log.error("FTP客户端初始化失败！", e);
	    throw new FTP4JException(e);
	}
    }

    /**
     * <pre>
     * 从FTP服务器下载一个指定的资源到本地的目标位子；如果本地路径不存在则自动创建。
     * </pre>
     * 
     * @param client FTP客户端
     * @param src 远程指定文件或路径
     * @param dst 本地目标文件或目录
     */
    public static void download(FTPClient client, String src, String dst) {
	download(client, src, new File(dst));
    }

    /**
     * <pre>
     * 从FTP服务器下载一个指定的资源到本地的目标位子；如果本地路径不存在则自动创建。
     * </pre>
     * 
     * @param client FTP客户端
     * @param src 远程指定文件或路径
     * @param dst 本地目标文件或目录
     */
    public static void download(FTPClient client, String src, File dst) {
	int ftpFileType = isExist(client, src);
	if (ftpFileType == -1) {
	    throw new FTP4JException(src + " --> 文件不存在！");
	}
	if (FTPFile.TYPE_DIRECTORY == ftpFileType) {
	    downloadDirectory(client, src, dst);
	}
	else {
	    downloadFile(client, src, dst);
	}
    }

    /**
     * <pre>
     * 从FTP服务器下载一个指定的目录到本地的目标路径下；如果本地路径不存在则自动创建。
     * </pre>
     * 
     * @param client FTP客户端
     * @param src 远程指定目录
     * @param dst 本地目标路径
     */
    private static void downloadDirectory(FTPClient client, String src, File dst) {
	if (!src.endsWith("/")) src = src.concat("/");

	FTPFile[] files = null;
	try {
	    files = client.list(src);
	}
	catch (Exception e) {
	    throw new FTP4JException(e);
	}

	String srcNew = null;
	File dstNew = null;
	for (FTPFile file : files) {
	    srcNew = src + file.getName();
	    dstNew = new File(dst.getPath() + File.separator + file.getName());
	    if (FTPFile.TYPE_DIRECTORY != isExist(client, srcNew)) {
		// 文件下载
		downloadFile(client, srcNew, dstNew);
	    }
	    else { // 目录下载
		dstNew.mkdir();
		log.debug(dst.getAbsolutePath() + " is done? -> " + dstNew.isDirectory());
		downloadDirectory(client, srcNew, dstNew);
	    }
	}
    }

    /**
     * <pre>
     * 从FTP服务器下载一个指定的文件为本地的目标文件；如果本地路径不存在则自动创建。
     * </pre>
     * 
     * @param client FTP客户端
     * @param src 远程指定文件
     * @param dst 本地目标文件
     */
    private static void downloadFile(FTPClient client, String src, File dst) {
	dst.getParentFile().mkdirs();
	OutputStream os = null;
	try {
	    download(client, src, os = new BufferedOutputStream(new FileOutputStream(dst)), 0l);
	}
	catch (FileNotFoundException e) {
	    throw new FTP4JException(e);
	}
	finally {
	    log.debug(dst.getAbsolutePath() + " is done? -> " + dst.isFile());
	    IOUtils.closeQuietly(os);
	}
    }

    /**
     * <pre>
     * 从FTP服务器下载一个指定文件到目标输出流；
     * </pre>
     * 
     * @param client FTP客户端
     * @param src 远程指定文件
     * @param dst 目标输出流
     */
    public static void download(FTPClient client, String src, OutputStream dst) {
	download(client, src, dst, 0l);
    }

    /**
     * <pre>
     * 从FTP服务器下载一个指定文件到目标输出流；
     * 该方法提供断点传输参数：
     * 	1.restartAt大于0时：启用断点传输，从restartAt指定的断点值开始下载；提前需要保证服务器支持这个功能！
     * 	2.restartAt等于0时：不用断点传输，采用普通方式下载。
     * </pre>
     * 
     * @param client FTP客户端
     * @param src 远程指定文件
     * @param dst 目标输出流
     * @param restartAt 断点传输值，等于零时表示采用普通方式下载。
     */
    public static void download(FTPClient client, String src, OutputStream dst, long restartAt) {
	int ftpFileType = isExist(client, src);
	if (ftpFileType == -1) {
	    throw new FTP4JException(src + " --> 文件不存在！");
	}
	if (ftpFileType == 1) {
	    throw new FTP4JException(src + " --> 些方法不支持目录下载功能！");
	}
	try {
	    client.download(src, dst, restartAt, new MyTransferListener());
	}
	catch (Exception e) {
	    throw new FTP4JException(e);
	}
    }

    /**
     * <pre>
     * 将本地的指定资源上传到FTP服务器的目标位子。
     * </pre>
     * 
     * @param client FTP客户端
     * @param dst 远程目标文件或路径
     * @param src 本地指定文件或目录
     */
    public static void upload(FTPClient client, String dst, String src) {
	upload(client, dst, new File(src));
    }

    /**
     * <pre>
     * 将本地的指定资源上传到FTP服务器的目标位子。
     * </pre>
     * 
     * @param client FTP客户端
     * @param dst 远程目标文件或路径
     * @param src 本地指定文件或目录
     */
    public static void upload(FTPClient client, String dst, File src) {
	if (!src.exists()) throw new FTP4JException("待上传资源 " + src.getPath() + " 不存在！");
	if (src.isFile()) {
	    uploadFile(client, dst, src);
	}
	else {
	    uploadDirectory(client, dst, src);
	}
    }

    /**
     * <pre>
     * 将本地的指定文件夹的内容上传到FTP服务器的目标路径下。
     * </pre>
     * 
     * @param client FTP客户端
     * @param dst 远程目标路径
     * @param src 本地指定文件夹
     */
    private static void uploadDirectory(FTPClient client, String dst, File src) {
	String localPath = src.getPath() + File.separator, dstNew = null;
	if (!dst.endsWith("/")) dst = dst.concat("/");

	File[] files = src.listFiles(); // 列表
	File[] filefs = FileFilterUtils.filter(FileFilterUtils.fileFileFilter(), files); // 文件列表
	File[] fileds = FileFilterUtils.filter(FileFilterUtils.directoryFileFilter(), files); // 目录列表

	for (File file : filefs) { // 上传文件列表
	    dstNew = file.getPath().replace(localPath, "");
	    dstNew = dst + dstNew;
	    uploadFile(client, dstNew, file);
	}

	for (File file : fileds) { // 创建目录列表
	    dstNew = file.getPath().replace(localPath, "");
	    dstNew = dst + dstNew;
	    createDirectory(client, dstNew);
	    uploadDirectory(client, dstNew, file);
	}
    }

    /**
     * <pre>
     * 将本地的指定文件上传为FTP服务器的目标文件。
     * </pre>
     * 
     * @param client FTP客户端
     * @param dst 远程目标文件
     * @param src 本地指定文件
     */
    private static void uploadFile(FTPClient client, String dst, File src) {
	InputStream is = null;
	try {
	    upload(client, dst, is = new BufferedInputStream(new FileInputStream(src)), 0l);
	}
	catch (FileNotFoundException e) {
	    throw new FTP4JException(e);
	}
	finally {
	    IOUtils.closeQuietly(is);
	}
    }

    /**
     * <pre>
     * 将指定输入流以“流”方式上传为FTP服务器上的目标文件；
     * 该方法提供断点传输参数：
     * 	1.restartAt大于0时：启用断点传输，从restartAt指定的断点值开始上传；提前需要保证服务器支持这个功能！
     * 	2.restartAt等于0时：不用断点传输，采用普通方式上传。
     * </pre>
     * 
     * @param client FTP客户端
     * @param dst 远程目标文件
     * @param src 指定输入流
     * @param restartAt 断点传输值，等于零时表示采用普通方式下载。
     */
    public static void upload(FTPClient client, String dst, InputStream src, long restartAt) {
	try {
	    client.upload(dst, src, restartAt, restartAt, new MyTransferListener());
	}
	catch (Exception e) {
	    throw new FTP4JException(e);
	}
    }

    /**
     * 创建FTP目录。
     * 
     * @param client FTP客户端
     * @param remotePath 远程目标路径
     */
    public static void createDirectory(FTPClient client, String remotePath) {
	try {
	    client.createDirectory(remotePath);
	}
	catch (Exception e) {
	    throw new FTP4JException(e);
	}
    }

    /**
     * 切换到指定FTP目录。
     * 
     * @param client FTP客户端
     * @param remotePath 远程目标路径
     */
    public static void changeDirectory(FTPClient client, String remotePath) {
	try {
	    client.changeDirectory(remotePath);
	}
	catch (Exception e) {
	    throw new FTP4JException(e);
	}
    }

    /**
     * FTP客户端当前的操作路径。
     * 
     * @param client FTP客户端
     * @return 当前路径。
     */
    public static String currentDirectory(FTPClient client) {
	try {
	    return client.currentDirectory();
	}
	catch (Exception e) {
	    throw new FTP4JException(e);
	}
    }

    /**
     * 删除FTP服务器上的指定文件或目录。
     * 
     * @param client FTP客户端
     * @param remotePath 远程目标文件或目录
     */
    public static void delete(FTPClient client, String remotePath) {
	int ftpFileType = isExist(client, remotePath);
	if (ftpFileType == -1) {
	    throw new FTP4JException(remotePath + " --> 文件不存在！");
	}
	try {
	    if (ftpFileType == 1) client.deleteDirectory(remotePath);
	    else client.deleteFile(remotePath);
	}
	catch (Exception e) {
	    throw new FTP4JException(e);
	}
    }

    /**
     * <pre>
     * 判断一个FTP路径是否存在；
     * 如果存在返回类型(FTPFile.TYPE_FILE=0、FTPFile.TYPE_DIRECTORY=1、FTPFile.TYPE_LINK=2) 如果文件不存在，则返回一个-1。
     * </pre>
     * 
     * @param client FTP客户端
     * @param remotePath FTP文件或文件夹路径
     * @return 存在时候返回类型值(文件0，文件夹1，连接2)，不存在则返回-1
     */
    public static int isExist(FTPClient client, String remotePath) {
	FTPFile[] files = null;

	try {
	    files = client.list(remotePath);
	}
	catch (Exception e) {
	    return -1;
	}

	if (files.length == 0) { // 有可能是空目录。
	    String oldPath = null;
	    try { // 倘若成功切换目录——表示就是目录。
		oldPath = client.currentDirectory();
		client.changeDirectory(remotePath);
		client.changeDirectory(oldPath);
		return FTPFile.TYPE_DIRECTORY;
	    }
	    catch (Exception e) {
		return -1;
	    }
	}

	if (files.length == 1) { // 文件？目录？呵呵。
	    FTPFile file = files[0];
	    if (file.getType() == FTPFile.TYPE_FILE) {
		// 防止误判断 --> 只有一个文件的目录。
		return remotePath.endsWith(file.getName()) ? FTPFile.TYPE_FILE : FTPFile.TYPE_DIRECTORY;
	    }
	    else {
		return file.getType();
	    }
	}

	// 一个路径下存在多个对象，肯定是文件夹。
	return FTPFile.TYPE_DIRECTORY;
    }

    /**
     * 关闭FTP连接，关闭时候像服务器发送一条关闭命令
     * 
     * @param client FTP客户端
     * @return 关闭成功，或者链接已断开，或者链接为null时返回true，通过两次关闭都失败时返回false
     */
    public static boolean closeQuietly(FTPClient client) {
	if (client == null || !client.isConnected()) return true;

	try {
	    client.logout();
	}
	catch (Exception e) {
	    log.error(e.getMessage(), e);
	}

	try {
	    client.disconnect(true);
	    return true;
	}
	catch (Exception e) {
	    log.error(e.getMessage(), e);
	}

	try {
	    client.disconnect(false);
	    return true;
	}
	catch (Exception e) {
	    log.error(e.getMessage(), e);
	}

	return false;
    }

}
